package tableaux;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
//import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import option.KBVocabulary;

import tableaux.backjumping.DependencyImp;
import tableaux.backjumping.DependencyImpMax;

import entities.*;
import entities.concept.*;

public class Branching {

	private Individual branchInd;
	private LinkedList<Concept> possibleConcepts;
	private LinkedList<Concept> branchConcepts;
	private OrConcept originalDisjunction;
	private Concept lastChosenConcept;
	private BranchChanges bc;
	private Branching fatherBranch;
	private int branchOrder;
	private Set<Integer> depSetInc;
	private int depthCounter = 0;
	static final private OrSelectStrategies defaultStrategy = OrSelectStrategies.GuardsFirst;

	private static int order = 0;

	public Branching(Individual ind, OrConcept oc) {
		this.setBranchInd(ind);
		this.depSetInc = new HashSet<Integer>();
		this.originalDisjunction = oc;
		Set<Concept> cons = oc.getConceptSet();
		this.possibleConcepts = new LinkedList<Concept>();
		this.possibleConcepts.addAll(cons);
		this.branchConcepts = new LinkedList<Concept>();
		this.branchConcepts.addAll(cons);
		this.fatherBranch = null;
		bc = new BranchChanges(this);
		this.branchOrder = ++order;
		depthCounter = this.branchConcepts.size();
	}

	public void decreCounter() {
		this.depthCounter--;
	}

	public int getOrder() {
		return this.branchOrder;
	}

	public OrConcept getOriginalDisjunction() {
		return this.originalDisjunction;
	}

	public LinkedList<Concept> getBranchConcepts() {
		return branchConcepts;
	}

	// public Individual getBranchingIndividual(){
	// return this.getBranchInd();
	// }
	public BranchChanges getBranchChanges() {
		return this.bc;
	}

	public Concept getLastChosenConcept() {
		return this.lastChosenConcept;
	}

	public boolean noChoices() {
		return this.possibleConcepts.isEmpty();
	}

	private enum OrSelectStrategies {
		Random, SortedLexiDesc, SortedLexiAsc, GuardsFirst
	}

	private Concept selectOrConceptStrategy(OrSelectStrategies st) {
		switch (st) {
		case Random:
			return this.possibleConcepts.poll();
		case SortedLexiDesc:
			Collections.sort(this.possibleConcepts, new Comparator<Concept>() {
				public int compare(Concept c1, Concept c2) {
					return c1.toString().compareTo(c2.toString());
				}
			});
			return this.possibleConcepts.poll();
		case SortedLexiAsc:
			Collections.sort(this.possibleConcepts, new Comparator<Concept>() {
				public int compare(Concept c1, Concept c2) {
					return c2.toString().compareTo(c1.toString());
				}
			});
			return this.possibleConcepts.poll();
		case GuardsFirst:
			// first select any branch concept that is a guard, may have to sort
			// first
			// i.e., DEF_r or DEF_f or DEF_a
			Collections.sort(this.possibleConcepts, new Comparator<Concept>() {
				public int compare(Concept c1, Concept c2) {
					return c2.toString().compareTo(c1.toString());
				}
			});
			Concept next = null;
			for (int i = 0; i < this.possibleConcepts.size(); i++) {
				Concept c = this.possibleConcepts.get(i);
				if (c instanceof PConcept
						&& ((PConcept) c).getPCName().startsWith(
								KBVocabulary.GUARD)) {
					next = c;
					break;
				}
			}
			if (next != null) {
				this.possibleConcepts.remove(next);
				return next;
			} else {
				return this.possibleConcepts.poll();
			}
		}
		return null;
	}

	// for A or B or C, if exploring B, add dep for B = dep for A union dep for
	// ~A etc.?
	// For the first disjunct, i.e., A, dep A= dep for the whole disjunction
	// only
	public boolean createBranch(Set<Integer> previousDeps) {
		if (previousDeps == null) {
			previousDeps = new HashSet<Integer>();
		}

		if (this.lastChosenConcept != null) {
			this.getBranchInd().removeLabels(this.lastChosenConcept);
		}
		if (noChoices()) {
			return false;
		}
		Concept c = this.selectOrConceptStrategy(defaultStrategy);
		this.lastChosenConcept = c;
		if (Completion.DEP_MAX) {
			if (noChoices() && this.depthCounter == 1) {
				// last branch
				// Set<Integer> deps = new HashSet<Integer>();
				// deps.addAll(depSetInc);
				// deps.remove(this.branchOrder);
				// DependencyImpMax.addDep(branchInd, c, deps);
			} else {
				// first n-1 branches
				DependencyImpMax.addDep(branchInd, c, this.branchOrder);
			}
		} else {
			this.depSetInc.addAll(previousDeps);
			Set<Integer> deps = new HashSet<Integer>();
			// first add dep set of the disjunction, then add the current branch
			// for n-1 branches only
			deps.addAll(DependencyImp.getDepSetOfConcept(branchInd,
					this.originalDisjunction));
			deps.add(this.branchOrder);
			if (noChoices() && this.depthCounter == 1) {
				// the last disjunct does not include the current branches in
				// its dep set
				deps.addAll(depSetInc);
				deps.remove(this.branchOrder);
			}
			DependencyImp.setDep(branchInd, c, deps);
		}

		// start recording all changes now
		BranchChanges.setRecordingStatus(true);
		// if(this.branchInd.getLabels().contains(c)){
		// System.out.println("The node already has this disjunt: "+c);
		// }
		this.branchInd.addLabels(c);
		// System.out.println("Created branch: "+this.branchOrder+" with "+c+" from"+this.originalDisjunction);
		if (BranchChanges.getRecordingStatus()) {
			this.getBranchChanges().addToLabel(getBranchInd(), c);
			// this.getBranchChanges().addToExpandedLabel(branchInd,
			// originalDisjunction);
		}
		return true;
	}

	// restore the branching ind (record all the changes)
	public void restore() {
		// revert all the changes
		bc.recoverRelations();
		bc.recoverNominalInds();
		bc.recoverLabel();
		// bc.recoverGCIappliedInds();
		bc.clearAddedLabels();
		bc.clearAllRoleRelations();
		bc.clearNominalInds();
		bc.clearGCIappliedInds();
		resetLastDisjunctAdded();
	}

	private void resetLastDisjunctAdded() {
		if (this.lastChosenConcept != null) {
			this.branchInd.removeLabels(this.lastChosenConcept);
		}
	}

	private void setBranchInd(Individual branchInd) {
		this.branchInd = branchInd;
	}

	public Individual getBranchInd() {
		return branchInd;
	}

	@Override
	public String toString() {
		String s = "";
		s += "Order: " + this.branchOrder + " [" + this.branchInd + "] ["
				+ this.lastChosenConcept + "] from " + this.originalDisjunction;
		return s;
	}

	public void setFatherBranch(Branching fatherBranch) {
		this.fatherBranch = fatherBranch;
	}

	public Branching getFatherBranch() {
		return fatherBranch;
	}

}
